/*
 * angular-ui-bootstrap
 * http://angular-ui.github.io/bootstrap/

 * Version: 1.2.5 - 2016-03-20
 * License: MIT
 */

as.value('$datepickerSuppressError', false)
    .value('aDatepickerAttributeWarning', true)
    .constant('$$DATEPICKER_CONFIG', {
        datepickerMode     : 'day',
        formatDay          : 'dd',
        formatMonth        : 'MMMM',
        formatYear         : 'yyyy',
        formatDayHeader    : 'EEE',
        formatDayTitle     : 'MMMM yyyy',
        formatMonthTitle   : 'yyyy',
        maxDate            : null,
        maxMode            : 'year',
        minDate            : null,
        minMode            : 'day',
        ngModelOptions     : {},
        shortcutPropagation: false,
        showWeeks          : true,
        yearColumns        : 5,
        yearRows           : 4
    })
    .run(["$templateCache", function ($templateCache) {
        $templateCache.put("datepicker/datepicker.html",
            "<div class=\"uib-datepicker\" ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
            "  <a-daypicker ng-switch-when=\"day\" tabindex=\"0\"></a-daypicker>\n" +
            "  <a-monthpicker ng-switch-when=\"month\" tabindex=\"0\"></a-monthpicker>\n" +
            "  <a-yearpicker ng-switch-when=\"year\" tabindex=\"0\"></a-yearpicker>\n" +
            "</div>\n" +
            "");
        $templateCache.put("datepicker/day.html",
            "<table class=\"uib-daypicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
            "  <thead>\n" +
            "    <tr>\n" +
            "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
            "      <th colspan=\"{{::5 + showWeeks}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" +
            "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
            "    </tr>\n" +
            "    <tr>\n" +
            "      <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" +
            "      <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" +
            "    </tr>\n" +
            "  </thead>\n" +
            "  <tbody>\n" +
            "    <tr class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\">\n" +
            "      <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
            "      <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n" +
            "        id=\"{{::dt.uid}}\"\n" +
            "        ng-class=\"::dt.customClass\">\n" +
            "        <button type=\"button\" class=\"btn btn-default btn-sm\"\n" +
            "          a-is-class=\"\n" +
            "            'btn-info' for selectedDt,\n" +
            "            'active' for activeDt\n" +
            "            on dt\"\n" +
            "          ng-click=\"select(dt.date)\"\n" +
            "          ng-disabled=\"::dt.disabled\"\n" +
            "          tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
            "      </td>\n" +
            "    </tr>\n" +
            "  </tbody>\n" +
            "</table>\n" +
            "");
        $templateCache.put("datepicker/month.html",
            "<table class=\"uib-monthpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
            "  <thead>\n" +
            "    <tr>\n" +
            "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
            "      <th><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" +
            "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
            "    </tr>\n" +
            "  </thead>\n" +
            "  <tbody>\n" +
            "    <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\">\n" +
            "      <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n" +
            "        id=\"{{::dt.uid}}\"\n" +
            "        ng-class=\"::dt.customClass\">\n" +
            "        <button type=\"button\" class=\"btn btn-default\"\n" +
            "          a-is-class=\"\n" +
            "            'btn-info' for selectedDt,\n" +
            "            'active' for activeDt\n" +
            "            on dt\"\n" +
            "          ng-click=\"select(dt.date)\"\n" +
            "          ng-disabled=\"::dt.disabled\"\n" +
            "          tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
            "      </td>\n" +
            "    </tr>\n" +
            "  </tbody>\n" +
            "</table>\n" +
            "");
        $templateCache.put("datepicker/popup.html",
            "<div>\n" +
            "  <ul class=\"uib-datepicker-popup dropdown-menu uib-position-measure\" dropdown-nested ng-if=\"isOpen\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" +
            "    <li ng-transclude></li>\n" +
            "    <li ng-if=\"showButtonBar\" class=\"uib-button-bar\">\n" +
            "      <span class=\"btn-group pull-left\">\n" +
            "        <button type=\"button\" class=\"btn btn-sm btn-info a-datepicker-current\" ng-click=\"select('today', $event)\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
            "        <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null, $event)\">{{ getText('clear') }}</button>\n" +
            "      </span>\n" +
            "      <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close($event)\">{{ getText('close') }}</button>\n" +
            "    </li>\n" +
            "  </ul>\n" +
            "</div>\n" +
            "");
        $templateCache.put("datepicker/year.html",
            "<table class=\"uib-yearpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
            "  <thead>\n" +
            "    <tr>\n" +
            "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
            "      <th colspan=\"{{::columns - 2}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" +
            "      <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
            "    </tr>\n" +
            "  </thead>\n" +
            "  <tbody>\n" +
            "    <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\">\n" +
            "      <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n" +
            "        id=\"{{::dt.uid}}\"\n" +
            "        ng-class=\"::dt.customClass\">\n" +
            "        <button type=\"button\" class=\"btn btn-default\"\n" +
            "          a-is-class=\"\n" +
            "            'btn-info' for selectedDt,\n" +
            "            'active' for activeDt\n" +
            "            on dt\"\n" +
            "          ng-click=\"select(dt.date)\"\n" +
            "          ng-disabled=\"::dt.disabled\"\n" +
            "          tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
            "      </td>\n" +
            "    </tr>\n" +
            "  </tbody>\n" +
            "</table>\n" +
            "");
    }])
    .controller('$$DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', '$$DATEPICKER_CONFIG', '$datepickerSuppressError', 'aDatepickerAttributeWarning', '$$dateParser',
        function ($scope, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerSuppressError, datepickerAttributeWarning, dateParser) {
            var self           = this,
                ngModelCtrl    = {$setViewValue: angular.noop}, // nullModelCtrl;
                ngModelOptions = {},
                watchListeners = [],
                optionsUsed    = !!$attrs.datepickerOptions;

            // Modes chain
            this.modes = ['day', 'month', 'year'];

            if (optionsUsed) {
                [
                    'customClass',
                    'dateDisabled',
                    'datepickerMode',
                    'formatDay',
                    'formatDayHeader',
                    'formatDayTitle',
                    'formatMonth',
                    'formatMonthTitle',
                    'formatYear',
                    'initDate',
                    'maxDate',
                    'maxMode',
                    'minDate',
                    'minMode',
                    'showWeeks',
                    'shortcutPropagation',
                    'startingDay',
                    'yearColumns',
                    'yearRows'
                ].forEach(function (key) {
                    switch (key) {
                        case 'customClass':
                        case 'dateDisabled':
                            $scope[key] = $scope.datepickerOptions[key] || angular.noop;
                            break;
                        case 'datepickerMode':
                            $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ?
                                $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode;
                            break;
                        case 'formatDay':
                        case 'formatDayHeader':
                        case 'formatDayTitle':
                        case 'formatMonth':
                        case 'formatMonthTitle':
                        case 'formatYear':
                            self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
                                $interpolate($scope.datepickerOptions[key])($scope.$parent) :
                                datepickerConfig[key];
                            break;
                        case 'showWeeks':
                        case 'shortcutPropagation':
                        case 'yearColumns':
                        case 'yearRows':
                            self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
                                $scope.datepickerOptions[key] : datepickerConfig[key];
                            break;
                        case 'startingDay':
                            if (angular.isDefined($scope.datepickerOptions.startingDay)) {
                                self.startingDay = $scope.datepickerOptions.startingDay;
                            } else if (angular.isNumber(datepickerConfig.startingDay)) {
                                self.startingDay = datepickerConfig.startingDay;
                            } else {
                                self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
                            }

                            break;
                        case 'maxDate':
                        case 'minDate':
                            if ($scope.datepickerOptions[key]) {
                                $scope.$watch(function () {
                                    return $scope.datepickerOptions[key];
                                }, function (value) {
                                    if (value) {
                                        if (angular.isDate(value)) {
                                            self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
                                        } else {
                                            self[key] = new Date(dateFilter(value, 'medium'));
                                        }
                                    } else {
                                        self[key] = null;
                                    }

                                    self.refreshView();
                                });
                            } else {
                                self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
                            }

                            break;
                        case 'maxMode':
                        case 'minMode':
                            if ($scope.datepickerOptions[key]) {
                                $scope.$watch(function () {
                                    return $scope.datepickerOptions[key];
                                }, function (value) {
                                    self[key] = $scope[key] = angular.isDefined(value) ? value : datepickerOptions[key];
                                    if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) ||
                                        key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) {
                                        $scope.datepickerMode = self[key];
                                        $scope.datepickerOptions.datepickerMode = self[key];
                                    }
                                });
                            } else {
                                self[key] = $scope[key] = datepickerConfig[key] || null;
                            }

                            break;
                        case 'initDate':
                            if ($scope.datepickerOptions.initDate) {
                                self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date();
                                $scope.$watch(function () {
                                    return $scope.datepickerOptions.initDate;
                                }, function (initDate) {
                                    if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
                                        self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
                                        self.refreshView();
                                    }
                                });
                            } else {
                                self.activeDate = new Date();
                            }
                    }
                });
            } else {
                // Interpolated configuration attributes
                angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle'], function (key) {
                    self[key] = angular.isDefined($attrs[key]) ? $interpolate($attrs[key])($scope.$parent) : datepickerConfig[key];

                    if (angular.isDefined($attrs[key]) && datepickerAttributeWarning) {
                        $log.warn('a-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead');
                    }
                });

                // Evaled configuration attributes
                angular.forEach(['showWeeks', 'yearRows', 'yearColumns', 'shortcutPropagation'], function (key) {
                    self[key] = angular.isDefined($attrs[key]) ?
                        $scope.$parent.$eval($attrs[key]) : datepickerConfig[key];

                    if (angular.isDefined($attrs[key]) && datepickerAttributeWarning) {
                        $log.warn('a-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead');
                    }
                });

                angular.forEach(['dateDisabled', 'customClass'], function (key) {
                    if (angular.isDefined($attrs[key]) && datepickerAttributeWarning) {
                        $log.warn('a-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead');
                    }
                });

                if (angular.isDefined($attrs.startingDay)) {
                    if (datepickerAttributeWarning) {
                        $log.warn('a-datepicker startingDay attribute usage is deprecated, use datepicker-options attribute instead');
                    }

                    self.startingDay = $scope.$parent.$eval($attrs.startingDay);
                } else if (angular.isNumber(datepickerConfig.startingDay)) {
                    self.startingDay = datepickerConfig.startingDay;
                } else {
                    self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
                }

                // Watchable date attributes
                angular.forEach(['minDate', 'maxDate'], function (key) {
                    if ($attrs[key]) {
                        if (datepickerAttributeWarning) {
                            $log.warn('a-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead');
                        }

                        watchListeners.push($scope.$parent.$watch($attrs[key], function (value) {
                            if (value) {
                                if (angular.isDate(value)) {
                                    self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
                                } else {
                                    self[key] = new Date(dateFilter(value, 'medium'));
                                }
                            } else {
                                self[key] = null;
                            }

                            self.refreshView();
                        }));
                    } else {
                        self[key] = datepickerConfig[key] ? dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : null;
                    }
                });

                angular.forEach(['minMode', 'maxMode'], function (key) {
                    if ($attrs[key]) {
                        if (datepickerAttributeWarning) {
                            $log.warn('a-datepicker ' + key + ' attribute usage is deprecated, use datepicker-options attribute instead');
                        }

                        watchListeners.push($scope.$parent.$watch($attrs[key], function (value) {
                            self[key] = $scope[key] = angular.isDefined(value) ? value : $attrs[key];
                            if (key === 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key]) ||
                                key === 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key])) {
                                $scope.datepickerMode = self[key];
                            }
                        }));
                    } else {
                        self[key] = $scope[key] = datepickerConfig[key] || null;
                    }
                });

                if (angular.isDefined($attrs.initDate)) {
                    if (datepickerAttributeWarning) {
                        $log.warn('a-datepicker initDate attribute usage is deprecated, use datepicker-options attribute instead');
                    }

                    var initDate = dateParser.fromTimezone($scope.$parent.$eval($attrs.initDate), ngModelOptions.timezone);
                    this.activeDate = !isNaN(initDate) ? initDate : new Date();
                    watchListeners.push($scope.$parent.$watch($attrs.initDate, function (initDate) {
                        if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
                            initDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
                            self.activeDate = !isNaN(initDate) ? initDate : new Date();
                            self.refreshView();
                        }
                    }));
                } else {
                    this.activeDate = new Date();
                }

                if ($attrs.datepickerMode && datepickerAttributeWarning) {
                    $log.warn('a-datepicker datepickerMode attribute usage is deprecated, use datepicker-options attribute instead');
                }

                $scope.datepickerMode = $scope.datepickerMode ||
                    datepickerConfig.datepickerMode;
            }

            $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);

            $scope.disabled = angular.isDefined($attrs.disabled) || false;
            if (angular.isDefined($attrs.ngDisabled)) {
                watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function (disabled) {
                    $scope.disabled = disabled;
                    self.refreshView();
                }));
            }

            $scope.isActive = function (dateObject) {
                if (self.compare(dateObject.date, self.activeDate) === 0) {
                    $scope.activeDateId = dateObject.uid;
                    return true;
                }
                return false;
            };

            this.init = function (ngModelCtrl_) {
                ngModelCtrl = ngModelCtrl_;
                ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;

                this.activeDate = ngModelCtrl.$modelValue ?
                    dateParser.fromTimezone(new Date(ngModelCtrl.$modelValue), ngModelOptions.timezone) :
                    dateParser.fromTimezone(new Date(), ngModelOptions.timezone);

                ngModelCtrl.$render = function () {
                    self.render();
                };
            };

            this.render = function () {
                if (ngModelCtrl.$viewValue) {
                    var date    = new Date(ngModelCtrl.$viewValue),
                        isValid = !isNaN(date);

                    if (isValid) {
                        this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
                    } else if (!$datepickerSuppressError) {
                        $log.error('Datepicker directive: "ng-model" value must be a Date object');
                    }
                }
                this.refreshView();
            };

            this.refreshView = function () {
                if (this.element) {
                    $scope.selectedDt = null;
                    this._refreshView();
                    if ($scope.activeDt) {
                        $scope.activeDateId = $scope.activeDt.uid;
                    }

                    var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
                    date = dateParser.fromTimezone(date, ngModelOptions.timezone);
                    ngModelCtrl.$setValidity('dateDisabled', !date ||
                        this.element && !this.isDisabled(date));
                }
            };

            this.createDateObject = function (date, format) {
                var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
                model = dateParser.fromTimezone(model, ngModelOptions.timezone);
                var today = new Date();
                today = dateParser.fromTimezone(today, ngModelOptions.timezone);
                var time = this.compare(date, today);
                var dt = {
                    date       : date,
                    label      : dateParser.filter(date, format),
                    selected   : model && this.compare(date, model) === 0,
                    disabled   : this.isDisabled(date),
                    past       : time < 0,
                    current    : time === 0,
                    future     : time > 0,
                    customClass: this.customClass(date) || null
                };

                if (model && this.compare(date, model) === 0) {
                    $scope.selectedDt = dt;
                }

                if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
                    $scope.activeDt = dt;
                }

                return dt;
            };

            this.isDisabled = function (date) {
                return $scope.disabled ||
                    this.minDate && this.compare(date, this.minDate) < 0 ||
                    this.maxDate && this.compare(date, this.maxDate) > 0 ||
                    $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
            };

            this.customClass = function (date) {
                return $scope.customClass({date: date, mode: $scope.datepickerMode});
            };

            // Split array into smaller arrays
            this.split = function (arr, size) {
                var arrays = [];
                while (arr.length > 0) {
                    arrays.push(arr.splice(0, size));
                }
                return arrays;
            };

            $scope.select = function (date) {
                if ($scope.datepickerMode === self.minMode) {
                    var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
                    dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
                    dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
                    ngModelCtrl.$setViewValue(dt);
                    ngModelCtrl.$render();
                } else {
                    self.activeDate = date;
                    setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]);

                    $scope.$emit('uib:datepicker.mode');
                }
            };

            $scope.move = function (direction) {
                var year  = self.activeDate.getFullYear() + direction * (self.step.years || 0),
                    month = self.activeDate.getMonth() + direction * (self.step.months || 0);
                self.activeDate.setFullYear(year, month, 1);
                self.refreshView();
            };

            $scope.toggleMode = function (direction) {
                direction = direction || 1;

                if ($scope.datepickerMode === self.maxMode && direction === 1 ||
                    $scope.datepickerMode === self.minMode && direction === -1) {
                    return;
                }

                setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]);

                $scope.$emit('uib:datepicker.mode');
            };

            // Key event mapper
            $scope.keys = {13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down'};

            var focusElement = function () {
                self.element[0].focus();
            };

            // Listen for focus requests from popup directive
            $scope.$on('uib:datepicker.focus', focusElement);

            $scope.keydown = function (evt) {
                var key = $scope.keys[evt.which];

                if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
                    return;
                }

                evt.preventDefault();
                if (!self.shortcutPropagation) {
                    evt.stopPropagation();
                }

                if (key === 'enter' || key === 'space') {
                    if (self.isDisabled(self.activeDate)) {
                        return; // do nothing
                    }
                    $scope.select(self.activeDate);
                } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
                    $scope.toggleMode(key === 'up' ? 1 : -1);
                } else {
                    self.handleKeyDown(key, evt);
                    self.refreshView();
                }
            };

            $scope.$on("$destroy", function () {
                //Clear all watch listeners on destroy
                while (watchListeners.length) {
                    watchListeners.shift()();
                }
            });

            function setMode(mode) {
                $scope.datepickerMode = mode;
                if (optionsUsed) {
                    $scope.datepickerOptions.datepickerMode = mode;
                }
            }
        }])
    .controller('$$DaypickerController', ['$scope', '$element', 'dateFilter', function (scope, $element, dateFilter) {
        var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

        this.step = {months: 1};
        this.element = $element;
        function getDaysInMonth(year, month) {
            return month === 1 && year % 4 === 0 &&
            (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
        }

        this.init = function (ctrl) {
            angular.extend(ctrl, this);
            scope.showWeeks = ctrl.showWeeks;
            ctrl.refreshView();
        };

        this.getDates = function (startDate, n) {
            var dates = new Array(n), current = new Date(startDate), i = 0, date;
            while (i < n) {
                date = new Date(current);
                dates[i++] = date;
                current.setDate(current.getDate() + 1);
            }
            return dates;
        };

        this._refreshView = function () {
            var year            = this.activeDate.getFullYear(),
                month           = this.activeDate.getMonth(),
                firstDayOfMonth = new Date(this.activeDate);

            firstDayOfMonth.setFullYear(year, month, 1);

            var difference                    = this.startingDay - firstDayOfMonth.getDay(),
                numDisplayedFromPreviousMonth = difference > 0 ?
                7 - difference : -difference,
                firstDate                     = new Date(firstDayOfMonth);

            if (numDisplayedFromPreviousMonth > 0) {
                firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
            }

            // 42 is the number of days on a six-week calendar
            var days = this.getDates(firstDate, 42);
            for (var i = 0; i < 42; i++) {
                days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
                    secondary: days[i].getMonth() !== month,
                    uid      : scope.uniqueId + '-' + i
                });
            }

            scope.labels = new Array(7);
            for (var j = 0; j < 7; j++) {
                scope.labels[j] = {
                    abbr: dateFilter(days[j].date, this.formatDayHeader),
                    full: dateFilter(days[j].date, 'EEEE')
                };
            }

            scope.title = dateFilter(this.activeDate, this.formatDayTitle);
            scope.rows = this.split(days, 7);

            if (scope.showWeeks) {
                scope.weekNumbers = [];
                var thursdayIndex = (4 + 7 - this.startingDay) % 7,
                    numWeeks      = scope.rows.length;
                for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
                    scope.weekNumbers.push(
                        getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
                }
            }
        };

        this.compare = function (date1, date2) {
            var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
            var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
            _date1.setFullYear(date1.getFullYear());
            _date2.setFullYear(date2.getFullYear());
            return _date1 - _date2;
        };

        function getISO8601WeekNumber(date) {
            var checkDate = new Date(date);
            checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
            var time = checkDate.getTime();
            checkDate.setMonth(0); // Compare with Jan 1
            checkDate.setDate(1);
            return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
        }

        this.handleKeyDown = function (key, evt) {
            var date = this.activeDate.getDate();

            if (key === 'left') {
                date = date - 1;
            } else if (key === 'up') {
                date = date - 7;
            } else if (key === 'right') {
                date = date + 1;
            } else if (key === 'down') {
                date = date + 7;
            } else if (key === 'pageup' || key === 'pagedown') {
                var month = this.activeDate.getMonth() + (key === 'pageup' ? -1 : 1);
                this.activeDate.setMonth(month, 1);
                date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
            } else if (key === 'home') {
                date = 1;
            } else if (key === 'end') {
                date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
            }
            this.activeDate.setDate(date);
        };
    }])
    .controller('$$MonthpickerController', ['$scope', '$element', 'dateFilter', function (scope, $element, dateFilter) {
        this.step = {years: 1};
        this.element = $element;

        this.init = function (ctrl) {
            angular.extend(ctrl, this);
            ctrl.refreshView();
        };

        this._refreshView = function () {
            var months = new Array(12),
                year   = this.activeDate.getFullYear(),
                date;

            for (var i = 0; i < 12; i++) {
                date = new Date(this.activeDate);
                date.setFullYear(year, i, 1);
                months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
                    uid: scope.uniqueId + '-' + i
                });
            }

            scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
            scope.rows = this.split(months, 3);
        };

        this.compare = function (date1, date2) {
            var _date1 = new Date(date1.getFullYear(), date1.getMonth());
            var _date2 = new Date(date2.getFullYear(), date2.getMonth());
            _date1.setFullYear(date1.getFullYear());
            _date2.setFullYear(date2.getFullYear());
            return _date1 - _date2;
        };

        this.handleKeyDown = function (key, evt) {
            var date = this.activeDate.getMonth();

            if (key === 'left') {
                date = date - 1;
            } else if (key === 'up') {
                date = date - 3;
            } else if (key === 'right') {
                date = date + 1;
            } else if (key === 'down') {
                date = date + 3;
            } else if (key === 'pageup' || key === 'pagedown') {
                var year = this.activeDate.getFullYear() + (key === 'pageup' ? -1 : 1);
                this.activeDate.setFullYear(year);
            } else if (key === 'home') {
                date = 0;
            } else if (key === 'end') {
                date = 11;
            }
            this.activeDate.setMonth(date);
        };
    }])
    .controller('$$YearpickerController', ['$scope', '$element', 'dateFilter', function (scope, $element, dateFilter) {
        var columns, range;
        this.element = $element;

        function getStartingYear(year) {
            return parseInt((year - 1) / range, 10) * range + 1;
        }

        this.yearpickerInit = function () {
            columns = this.yearColumns;
            range = this.yearRows * columns;
            this.step = {years: range};
        };

        this._refreshView = function () {
            var years = new Array(range), date;

            for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
                date = new Date(this.activeDate);
                date.setFullYear(start + i, 0, 1);
                years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
                    uid: scope.uniqueId + '-' + i
                });
            }

            scope.title = [years[0].label, years[range - 1].label].join(' - ');
            scope.rows = this.split(years, columns);
            scope.columns = columns;
        };

        this.compare = function (date1, date2) {
            return date1.getFullYear() - date2.getFullYear();
        };

        this.handleKeyDown = function (key, evt) {
            var date = this.activeDate.getFullYear();

            if (key === 'left') {
                date = date - 1;
            } else if (key === 'up') {
                date = date - columns;
            } else if (key === 'right') {
                date = date + 1;
            } else if (key === 'down') {
                date = date + columns;
            } else if (key === 'pageup' || key === 'pagedown') {
                date += (key === 'pageup' ? -1 : 1) * range;
            } else if (key === 'home') {
                date = getStartingYear(this.activeDate.getFullYear());
            } else if (key === 'end') {
                date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
            }
            this.activeDate.setFullYear(date);
        };
    }])
    .directive('aDatepicker', function () {
        return {
            replace     : true,
            templateUrl : function (element, attrs) {
                return attrs.templateUrl || 'datepicker/datepicker.html';
            },
            scope       : {
                datepickerMode     : '=?',
                datepickerOptions  : '=?',
                dateDisabled       : '&',
                customClass        : '&',
                shortcutPropagation: '&?'
            },
            require     : ['aDatepicker', '^ngModel'],
            controller  : '$$DatepickerController',
            controllerAs: 'datepicker',
            link        : function (scope, element, attrs, ctrls) {
                var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];

                datepickerCtrl.init(ngModelCtrl);
            }
        };
    })
    .directive('aDaypicker', function () {
        return {
            replace    : true,
            templateUrl: function (element, attrs) {
                return attrs.templateUrl || 'datepicker/day.html';
            },
            require    : ['^aDatepicker', 'aDaypicker'],
            controller : '$$DaypickerController',
            link       : function (scope, element, attrs, ctrls) {
                var datepickerCtrl = ctrls[0],
                    daypickerCtrl  = ctrls[1];

                daypickerCtrl.init(datepickerCtrl);
            }
        };
    })
    .directive('aMonthpicker', function () {
        return {
            replace    : true,
            templateUrl: function (element, attrs) {
                return attrs.templateUrl || 'datepicker/month.html';
            },
            require    : ['^aDatepicker', 'asMonthpicker'],
            controller : '$$MonthpickerController',
            link       : function (scope, element, attrs, ctrls) {
                var datepickerCtrl  = ctrls[0],
                    monthpickerCtrl = ctrls[1];

                monthpickerCtrl.init(datepickerCtrl);
            }
        };
    })
    .directive('aYearpicker', function () {
        return {
            replace    : true,
            templateUrl: function (element, attrs) {
                return attrs.templateUrl || 'datepicker/year.html';
            },
            require    : ['^aDatepicker', 'asYearpicker'],
            controller : '$$YearpickerController',
            link       : function (scope, element, attrs, ctrls) {
                var ctrl = ctrls[0];
                angular.extend(ctrl, ctrls[1]);
                ctrl.yearpickerInit();

                ctrl.refreshView();
            }
        };
    })
    .value('aDatepickerPopupAttributeWarning', true)
    .constant('aDatepickerPopupConfig', {
        altInputFormats           : [],
        appendToBody              : false,
        clearText                 : '清除',
        closeOnDateSelection      : true,
        closeText                 : '关闭',
        currentText               : '今天',
        datepickerPopup           : 'yyyy-MM-dd',
        datepickerPopupTemplateUrl: 'datepicker/popup.html',
        datepickerTemplateUrl     : 'datepicker/datepicker.html',
        html5Types                : {
            date            : 'yyyy-MM-dd',
            'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
            'month'         : 'yyyy-MM'
        },
        onOpenFocus               : true,
        showButtonBar             : true,
        placement                 : 'auto bottom-left'
    })
    .controller('$$DatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$$position', 'dateFilter', '$$dateParser', 'aDatepickerPopupConfig', '$timeout', '$$DATEPICKER_CONFIG', 'aDatepickerPopupAttributeWarning',
        function ($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, datepickerPopupAttributeWarning) {
            var cache            = {},
                isHtml5DateInput = false;
            var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
                datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
                ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];

            $scope.watchData = {};

            this.init = function (_ngModel_) {
                ngModel = _ngModel_;
                ngModelOptions = _ngModel_.$options || datepickerConfig.ngModelOptions;
                closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
                    $scope.$parent.$eval($attrs.closeOnDateSelection) :
                    datepickerPopupConfig.closeOnDateSelection;
                appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
                    $scope.$parent.$eval($attrs.datepickerAppendToBody) :
                    datepickerPopupConfig.appendToBody;
                onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
                    $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
                datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
                    $attrs.datepickerPopupTemplateUrl :
                    datepickerPopupConfig.datepickerPopupTemplateUrl;
                datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
                    $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
                altInputFormats = angular.isDefined($attrs.altInputFormats) ?
                    $scope.$parent.$eval($attrs.altInputFormats) :
                    datepickerPopupConfig.altInputFormats;

                $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
                    $scope.$parent.$eval($attrs.showButtonBar) :
                    datepickerPopupConfig.showButtonBar;

                if (datepickerPopupConfig.html5Types[$attrs.type]) {
                    dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
                    isHtml5DateInput = true;
                } else {
                    dateFormat = $attrs.aDatepickerPopup || datepickerPopupConfig.datepickerPopup;
                    $attrs.$observe('aDatepickerPopup', function (value, oldValue) {
                        var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
                        // Invalidate the $modelValue to ensure that formatters re-run
                        // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
                        if (newDateFormat !== dateFormat) {
                            dateFormat = newDateFormat;
                            ngModel.$modelValue = null;

                            if (!dateFormat) {
                                throw new Error('aDatepickerPopup must have a date format specified.');
                            }
                        }
                    });
                }

                if (!dateFormat) {
                    throw new Error('aDatepickerPopup must have a date format specified.');
                }

                if (isHtml5DateInput && $attrs.aDatepickerPopup) {
                    throw new Error('HTML5 date input types do not support custom formats.');
                }

                // popup element used to display calendar
                popupEl = angular.element('<div a-datepicker-popup-wrap><div a-datepicker></div></div>');
                $scope.ngModelOptions = angular.copy(ngModelOptions);
                $scope.ngModelOptions.timezone = null;
                if ($scope.ngModelOptions.updateOnDefault === true) {
                    $scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ?
                    $scope.ngModelOptions.updateOn + ' default' : 'default';
                }

                popupEl.attr({
                    'ng-model'        : 'date',
                    'ng-model-options': 'ngModelOptions',
                    'ng-change'       : 'dateSelection(date)',
                    'template-url'    : datepickerPopupTemplateUrl
                });

                // datepicker element
                datepickerEl = angular.element(popupEl.children()[0]);
                datepickerEl.attr('template-url', datepickerTemplateUrl);

                if (isHtml5DateInput) {
                    if ($attrs.type === 'month') {
                        datepickerEl.attr('datepicker-mode', '"month"');
                        datepickerEl.attr('min-mode', 'month');
                    }
                }

                if ($scope.datepickerOptions) {
                    datepickerEl.attr('datepicker-options', 'datepickerOptions');
                }

                angular.forEach(['minMode', 'maxMode', 'datepickerMode', 'shortcutPropagation'], function (key) {
                    if ($attrs[key]) {
                        if (datepickerPopupAttributeWarning) {
                            $log.warn('a-datepicker settings via a-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead');
                        }

                        var getAttribute = $parse($attrs[key]);
                        var propConfig = {
                            get: function () {
                                return getAttribute($scope.$parent);
                            }
                        };

                        datepickerEl.attr(cameltoDash(key), 'watchData.' + key);

                        // Propagate changes from datepicker to outside
                        if (key === 'datepickerMode') {
                            var setAttribute = getAttribute.assign;
                            propConfig.set = function (v) {
                                setAttribute($scope.$parent, v);
                            };
                        }

                        Object.defineProperty($scope.watchData, key, propConfig);
                    }
                });

                angular.forEach(['minDate', 'maxDate', 'initDate'], function (key) {
                    if ($attrs[key]) {
                        if (datepickerPopupAttributeWarning) {
                            $log.warn('a-datepicker settings via a-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead');
                        }

                        var getAttribute = $parse($attrs[key]);

                        watchListeners.push($scope.$parent.$watch(getAttribute, function (value) {
                            if (key === 'minDate' || key === 'maxDate') {
                                if (value === null) {
                                    cache[key] = null;
                                } else if (angular.isDate(value)) {
                                    cache[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
                                } else {
                                    cache[key] = new Date(dateFilter(value, 'medium'));
                                }

                                $scope.watchData[key] = value === null ? null : cache[key];
                            } else {
                                var date = value ? new Date(value) : new Date();
                                $scope.watchData[key] = dateParser.fromTimezone(date, ngModelOptions.timezone);
                            }
                        }));

                        datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
                    }
                });

                if ($attrs.dateDisabled) {
                    if (datepickerPopupAttributeWarning) {
                        $log.warn('a-datepicker settings via a-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead');
                    }

                    datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
                }

                angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', 'showWeeks', 'startingDay', 'yearRows', 'yearColumns'], function (key) {
                    if (angular.isDefined($attrs[key])) {
                        if (datepickerPopupAttributeWarning) {
                            $log.warn('a-datepicker settings via a-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead');
                        }

                        datepickerEl.attr(cameltoDash(key), $attrs[key]);
                    }
                });

                if ($attrs.customClass) {
                    if (datepickerPopupAttributeWarning) {
                        $log.warn('a-datepicker settings via a-datepicker-popup attributes are deprecated and will be removed in UI Bootstrap 1.3, use datepicker-options attribute instead');
                    }

                    datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
                }

                if (!isHtml5DateInput) {
                    // Internal API to maintain the correct ng-invalid-[key] class
                    ngModel.$$parserName = 'date';
                    ngModel.$validators.date = validator;
                    ngModel.$parsers.unshift(parseDate);
                    ngModel.$formatters.push(function (value) {
                        if (ngModel.$isEmpty(value)) {
                            $scope.date = value;
                            return value;
                        }

                        $scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);

                        if (angular.isNumber($scope.date)) {
                            $scope.date = new Date($scope.date);
                        }

                        return dateParser.filter($scope.date, dateFormat);
                    });
                } else {
                    ngModel.$formatters.push(function (value) {
                        $scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
                        return value;
                    });
                }

                // Detect changes in the view from the text box
                ngModel.$viewChangeListeners.push(function () {
                    $scope.date = parseDateString(ngModel.$viewValue);
                });

                $element.on('keydown', inputKeydownBind);

                $popup = $compile(popupEl)($scope);
                // Prevent jQuery cache memory leak (template is now redundant after linking)
                popupEl.remove();

                if (appendToBody) {
                    $document.find('body').append($popup);
                } else {
                    $element.after($popup);
                }

                $scope.$on('$destroy', function () {
                    if ($scope.isOpen === true) {
                        if (!$rootScope.$$phase) {
                            $scope.$apply(function () {
                                $scope.isOpen = false;
                            });
                        }
                    }

                    $popup.remove();
                    $element.off('keydown', inputKeydownBind);
                    $document.off('click', documentClickBind);
                    if (scrollParentEl) {
                        scrollParentEl.off('scroll', positionPopup);
                    }
                    angular.element($window).off('resize', positionPopup);

                    //Clear all watch listeners on destroy
                    while (watchListeners.length) {
                        watchListeners.shift()();
                    }
                });
            };

            $scope.getText = function (key) {
                return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
            };

            $scope.isDisabled = function (date) {
                if (date === 'today') {
                    date = dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
                }

                if ($scope.datepickerOptions) {
                    return $scope.datepickerOptions &&
                        $scope.datepickerOptions.minDate && $scope.compare(date, $scope.datepickerOptions.minDate) < 0 ||
                        $scope.datepickerOptions.maxDate && $scope.compare(date, $scope.datepickerOptions.maxDate) > 0;
                }

                return $scope.watchData.minDate && $scope.compare(date, cache.minDate) < 0 ||
                    $scope.watchData.maxDate && $scope.compare(date, cache.maxDate) > 0;
            };

            $scope.compare = function (date1, date2) {
                return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
            };

            // Inner change
            $scope.dateSelection = function (dt) {
                if (angular.isDefined(dt)) {
                    $scope.date = dt;
                }
                var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
                $element.val(date);
                ngModel.$setViewValue(date);

                if (closeOnDateSelection) {
                    $scope.isOpen = false;
                    $element[0].focus();
                }
            };

            $scope.keydown = function (evt) {
                if (evt.which === 27) {
                    evt.stopPropagation();
                    $scope.isOpen = false;
                    $element[0].focus();
                }
            };

            $scope.select = function (date, evt) {
                evt.stopPropagation();

                if (date === 'today') {
                    var today = new Date();
                    if (angular.isDate($scope.date)) {
                        date = new Date($scope.date);
                        date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
                    } else {
                        date = new Date(today.setHours(0, 0, 0, 0));
                    }
                }
                $scope.dateSelection(date);
            };

            $scope.close = function (evt) {
                evt.stopPropagation();

                $scope.isOpen = false;
                $element[0].focus();
            };

            $scope.disabled = angular.isDefined($attrs.disabled) || false;
            if ($attrs.ngDisabled) {
                watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function (disabled) {
                    $scope.disabled = disabled;
                }));
            }

            $scope.$watch('isOpen', function (value) {
                if (value) {
                    if (!$scope.disabled) {
                        $timeout(function () {
                            positionPopup();

                            if (onOpenFocus) {
                                $scope.$broadcast('uib:datepicker.focus');
                            }
                            $document.on('click', documentClickBind);

                            var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
                            if (appendToBody || $position.parsePlacement(placement)[2]) {
                                scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
                                if (scrollParentEl) {
                                    scrollParentEl.on('scroll', positionPopup);
                                }
                            } else {
                                scrollParentEl = null;
                            }

                            angular.element($window).on('resize', positionPopup);
                        }, 0, false);
                    } else {
                        $scope.isOpen = false;
                    }
                } else {
                    $document.off('click', documentClickBind);
                    if (scrollParentEl) {
                        scrollParentEl.off('scroll', positionPopup);
                    }
                    angular.element($window).off('resize', positionPopup);
                }
            });

            function cameltoDash(string) {
                return string.replace(/([A-Z])/g, function ($1) {
                    return '-' + $1.toLowerCase();
                });
            }

            function parseDateString(viewValue) {
                var date = dateParser.parse(viewValue, dateFormat, $scope.date);
                if (isNaN(date)) {
                    for (var i = 0; i < altInputFormats.length; i++) {
                        date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
                        if (!isNaN(date)) {
                            return date;
                        }
                    }
                }
                return date;
            }

            function parseDate(viewValue) {
                if (angular.isNumber(viewValue)) {
                    // presumably timestamp to date object
                    viewValue = new Date(viewValue);
                }

                if (!viewValue) {
                    return null;
                }

                if (angular.isDate(viewValue) && !isNaN(viewValue)) {
                    return viewValue;
                }

                if (angular.isString(viewValue)) {
                    var date = parseDateString(viewValue);
                    if (!isNaN(date)) {
                        return dateParser.toTimezone(date, ngModelOptions.timezone);
                    }
                }

                return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
            }

            function validator(modelValue, viewValue) {
                var value = modelValue || viewValue;

                if (!$attrs.ngRequired && !value) {
                    return true;
                }

                if (angular.isNumber(value)) {
                    value = new Date(value);
                }

                if (!value) {
                    return true;
                }

                if (angular.isDate(value) && !isNaN(value)) {
                    return true;
                }

                if (angular.isString(value)) {
                    return !isNaN(parseDateString(viewValue));
                }

                return false;
            }

            function documentClickBind(event) {
                if (!$scope.isOpen && $scope.disabled) {
                    return;
                }

                var popup = $popup[0];
                var dpContainsTarget = $element[0].contains(event.target);
                // The popup node may not be an element node
                // In some browsers (IE) only element nodes have the 'contains' function
                var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
                if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
                    $scope.$apply(function () {
                        $scope.isOpen = false;
                    });
                }
            }

            function inputKeydownBind(evt) {
                if (evt.which === 27 && $scope.isOpen) {
                    evt.preventDefault();
                    evt.stopPropagation();
                    $scope.$apply(function () {
                        $scope.isOpen = false;
                    });
                    $element[0].focus();
                } else if (evt.which === 40 && !$scope.isOpen) {
                    evt.preventDefault();
                    evt.stopPropagation();
                    $scope.$apply(function () {
                        $scope.isOpen = true;
                    });
                }
            }

            function positionPopup() {
                if ($scope.isOpen) {
                    var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
                    var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
                    var position = $position.positionElements($element, dpElement, placement, appendToBody);
                    dpElement.css({top: position.top + 'px', left: position.left + 'px'});
                    if (dpElement.hasClass('uib-position-measure')) {
                        dpElement.removeClass('uib-position-measure');
                    }
                }
            }

            $scope.$on('uib:datepicker.mode', function () {
                $timeout(positionPopup, 0, false);
            });
        }])
    .directive('aDatepickerPopup', function () {
        return {
            require   : ['ngModel', 'aDatepickerPopup'],
            controller: '$$DatepickerPopupController',
            scope     : {
                datepickerOptions: '=?',
                isOpen           : '=?',
                currentText      : '@',
                clearText        : '@',
                closeText        : '@',
                dateDisabled     : '&',
                customClass      : '&'
            },
            link      : function (scope, element, attrs, ctrls) {
                var ngModel = ctrls[0],
                    ctrl    = ctrls[1];

                ctrl.init(ngModel);
            }
        };
    })
    .directive('aDatepickerPopupWrap', function () {
        return {
            replace    : true,
            transclude : true,
            templateUrl: function (element, attrs) {
                return attrs.templateUrl || 'datepicker/popup.html';
            }
        };
    });